// Copyright 2013 Daniel de Kok
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.gitee.wsl.text.dictomaton


import com.gitee.wsl.collections.map.TreeMap

/**
 * State representation for the minimized deterministic acyclic automaton builder.
 */
internal class State {

    val transitions: TreeMap<Char, State> = TreeMap()
    private var d_final: Boolean
    private var d_recomputeHash: Boolean
    private var d_cachedHash = 0

    /**
     * Construct a state. The state will have no transitions and will be non-final.
     */
    init {
        d_final = false
        d_recomputeHash = true
    }

    /**
     * Add a transition to the state. If a transition with the provided character already
     * exists, it will be replaced.
     *
     * @param c The transition character.
     * @param s The to-state.
     */
    fun addTransition(c: Char, s: State) {
        transitions.put(c, s)
        d_recomputeHash = true
    }


    override fun hashCode(): Int {
        if (!d_recomputeHash) return d_cachedHash

        val prime = 31
        var result = 1
        result = prime * result + (if (d_final) 1231 else 1237)
        result = (prime * result + (transitionsHashCode()))

        d_recomputeHash = false
        d_cachedHash = result

        return result
    }


    override fun equals(obj: Any?): Boolean {
        if (this === obj) return true

        if (obj == null) return false

        if (this::class !== obj::class) return false

        val other = obj as State

        if (!d_recomputeHash && !other.d_recomputeHash && d_cachedHash != other.d_cachedHash) return false

        if (d_final != other.d_final) return false

        //if (transitions == null) return other.transitions == null

        return transitions == other.transitions
    }

    var isFinal: Boolean
        /**
         * Returns <tt>true</tt> if the state is a final state.
         *
         * @return <tt>true</tt> if the state is a final state, <tt>false</tt> otherwise.
         */
        get() = d_final
        /**
         * Set the 'finalness' of the state.
         *
         * @param finalState If <tt>true</tt>, the state is set to be final. Otherwise, it is non-final.
         */
        set(finalState) {
            d_final = finalState
            d_recomputeHash = true
        }

    /**
     * Returns <tt>true</tt> if the state has outgoing transitions.
     *
     * @return <tt>true</tt> if the state has outgoing transitions, <tt>false</tt> otherwise.
     */
    fun hasOutgoing(): Boolean {
        return transitions.isNotEmpty()
    }

    /**
     * Obtain the state that the last transition (the transition with the 'highest' character value)
     * points to.
     *
     * @return The to-state of the last transition.
     */
    fun lastState(): State? {
        val last = transitions.lastEntry()
        if (last == null) return null

        return last.value
    }

    /**
     * Set the [State] that the last transition points to.
     *
     * @param s The state.
     */
    fun setLastState(s: State) {
        val entry = transitions.lastEntry()?: return
        transitions.put(entry.key, s)
        d_recomputeHash = true
    }

    /**
     * Obtain the transitions of this state. This method does not return a copy. Modifying the transition
     * map may make the internal state inconsistent.
     *
     * @return The transition map.
     */
//    fun transitions(): TreeMap<Char, State>? {
//        return transitions
//    }

    /**
     * Follow a transition.
     *
     * @param c The character.
     * @return The target state of the transition, <tt>null</tt> if there is no transition with the given character.
     */
    fun move(c: Char): State? {
        return transitions[c]
    }

    /**
     * Return the hashcode 'computed' by [Object.hashCode].
     *
     * @return The hashcode.
     */
    private fun objectHashCode(): Int {
        return super.hashCode()
    }

    /**
     * Compute the hashcode for transitions. We do not rely on the[java.util.TreeMap.hashCode],
     * because it will recursively compute the hashcodes of the to-states, which is not necessary since
     * two states are only identical when they have the same symbols leading to exactly the same objects.
     *
     * @return The hashcode of the transition table.
     */
    private fun transitionsHashCode(): Int {
        var hc = 0

        for (e in transitions.entries) hc += e.key.hashCode() + e.value.objectHashCode()

        return hc
    }

}
